/**
 * Copyright (C) @2014 Webank Group Holding Limited
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package cn.webank.framework.biz.utils;

import java.util.ArrayList;
import java.util.List;
import java.util.Locale;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.support.ReloadableResourceBundleMessageSource;

import cn.webank.framework.biz.service.support.WeBankServiceDispatcher;
import cn.webank.framework.context.WeBankContext;
import cn.webank.framework.dto.AsyncRMBMessage;
import cn.webank.framework.dto.BizError;
import cn.webank.framework.dto.BizErrors;
import cn.webank.framework.dto.ResponseKeys;
import cn.webank.framework.dto.AsyncRMBMessage.MessageMode;
import cn.webank.framework.mapper.JsonMapper;
import cn.webank.framework.message.integration.RMBSao;
import cn.webank.framework.utils.WeBankContextUtil;
import cn.webank.rmb.destination.Destination;
import cn.webank.rmb.destination.SimpleDestination;
import cn.webank.rmb.message.AppHeader;
import cn.webank.rmb.message.Message;
import cn.webank.rmb.message.Ret;

/**
 * 服务工具类
 * 
 * @author jonyang
 *
 */
public class ServiceUtil {

	private static final JsonMapper jsonMapper = JsonMapper.nonEmptyMapper();
	private static final Logger LOG = LoggerFactory.getLogger(ServiceUtil.class);

	/**
	 * 构建服务执行任务
	 * 
	 * @param message
	 *            RMB消息
	 * @param dcnNo
	 *            dcn号
	 * @param serviceDispatcher
	 *            服务分发器
	 * @param rmbSao
	 *            rmb sao 服务
	 * @param bundleMessageSource
	 *            国际化消息管理器
	 * @param sysId
	 *            系统ID
	 * @param errors
	 *            业务异常
	 * @param locale
	 *            区域
	 * @return Runnable对象
	 */
	public static Runnable constructExcutorTask(final Message message, final String dcnNo,
			final WeBankServiceDispatcher serviceDispatcher, final RMBSao rmbSao,
			final ReloadableResourceBundleMessageSource bundleMessageSource, final String sysId, final BizErrors errors,
			final Locale locale) {

		Runnable r = new Runnable() {

			public void run() {
				if (LOG.isDebugEnabled()) {
					LOG.debug("execute task begin,bizSeqNo:" + message.getSysHeader().getBizSeqNo() + ",rmb message"
							+ jsonMapper.toJson(message));
				}
				AsyncRMBMessage<Object> asyncMessage = new AsyncRMBMessage<Object>(
						message.isSyn() ? MessageMode.P2P : MessageMode.PUBSUB);

				asyncMessage.setRmbMessage(message);

				Object resultDTO = null;
				try {
					// 1.set context bind thread
					WeBankContext context = new WeBankContext();
					context.setBizSeqNo(message.getSysHeader().getBizSeqNo());
					context.setDcnNo(dcnNo);// 有pulish的情况，可能没有dcnNo
					context.setOrgSysId(message.getSysHeader().getOrgSysId());
					context.setLastConsumerId(message.getSysHeader().getConsumerId());
					context.setLastConsumerSeqNo(message.getSysHeader().getConsumerSeqNo());
					context.setSysId(sysId);
					context.setReversalSeqNo(
							message.getAppHeader() == null ? null : message.getAppHeader().getReversalSeqNo());
					WeBankContextUtil.setContext(context);

					resultDTO = internalDispatchService(message, sysId, dcnNo, serviceDispatcher, errors);

					if (message.isSyn()) {
						if (errors.hasErrors()) {
							handleSyncMessageBizException(asyncMessage, errors, bundleMessageSource, locale);
						} else {
							handleSuccess(asyncMessage, sysId);
						}
					} else {
						if (errors.hasErrors()) {
							handleAyncMessageBizException(asyncMessage, errors, bundleMessageSource, locale);
						}
					}

				} catch (Exception e) {
					// 由业务服务记录错误报文
					LOG.error("bizSeqNo:" + message.getSysHeader().getBizSeqNo() + " distach service error:", e);
					if (message.isSyn()) {
						handleSysException(asyncMessage, e, sysId);
					}
				} finally {
					if (LOG.isDebugEnabled()) {
						LOG.debug("execute task begin,bizSeqNo:" + message.getSysHeader().getBizSeqNo()
								+ ",rmb message:" + jsonMapper.toJson(message));
					}
					WeBankContextUtil.unsetContext();
				}

				// 4.2 publish result message
				// 异步不需要publish
				if (message.isSyn()) {
					try {
						message.setDestination(null);
						if (LOG.isDebugEnabled()) {
							LOG.debug("sync message pulish begin,bizSeqNo:" + message.getSysHeader().getBizSeqNo()
									+ ",rmb message:" + jsonMapper.toJson(message));
						}
						asyncMessage.setRequestObject(resultDTO);
						asyncMessage.setMode(MessageMode.P2P);
						rmbSao.publish(asyncMessage);

					} catch (Exception e) {
						LOG.error("bizSeqNo:" + message.getSysHeader().getBizSeqNo() + " publish message error,message:"
								+ message.getContent(), e);
					} finally {
						if (LOG.isDebugEnabled()) {
							LOG.debug("sync message pulish end,bizSeqNo:" + message.getSysHeader().getBizSeqNo()
									+ ",rmb message:" + jsonMapper.toJson(message));
						}
					}
				}

				// LOG.debug("executor task end:" + message);

			}
		};

		return r;

	}

	public static Object internalDispatchService(final Message message, final String sysId, final String dcnNo,
			final WeBankServiceDispatcher serviceDispatcher, final BizErrors errors) {

		String serviceId = null;
		String scenario = null;
		String organizationId = message.getSysHeader().getOrganizationId();
		Destination destination = message.getDestination();
		String version = message.getSysHeader().getVersion();

		if (destination instanceof SimpleDestination) {
			serviceId = ((SimpleDestination) destination).getServiceOrEventId();
			scenario = ((SimpleDestination) destination).getScenario();
		}

		return serviceDispatcher.dispatch(organizationId, serviceId, scenario, version, message, errors);

	}

	private static void handleSuccess(AsyncRMBMessage<?> message, String sysId) {
		Ret ret = new Ret();
		ret.setCode(sysId + ResponseKeys.SUCCESS_CODE);
		ret.setMsg("");
		List<Ret> list = new ArrayList<Ret>();
		list.add(ret);
		message.setRetList(list);
		message.setRetStatus(AppHeader.RET_STATUS_SUCCESS);

	}

	private static void handleSysException(AsyncRMBMessage<?> message, Exception e, String sysId) {

		Ret ret = new Ret();
		ret.setCode(sysId + ResponseKeys.SYSTEM_ERROR_CODE);
		ret.setMsg(e.getMessage());
		List<Ret> list = new ArrayList<Ret>();
		list.add(ret);
		message.setRetList(list);
		message.setRetStatus(AppHeader.RET_STATUS_FAILURE);
	}

	private static void handleSyncMessageBizException(AsyncRMBMessage<?> asyncMessage, BizErrors errors,
			ReloadableResourceBundleMessageSource bundleMessageSource, Locale locale) {

		List<Ret> list = new ArrayList<Ret>();

		if (locale == null)
			locale = Locale.CHINESE;

		if (errors != null && errors.hasErrors()) {
			for (BizError error : errors.getAllErrors()) {
				Ret ret = new Ret();
				ret.setCode(error.getCode());
				ret.setMsg(bundleMessageSource.getMessage(error.getCode(), error.getArguments(),
						error.getDefaultMessage(), locale));
				list.add(ret);
			}
		}

		if (asyncMessage != null) {
			asyncMessage.setRetList(list);
			asyncMessage.setRetStatus(AppHeader.RET_STATUS_FAILURE);
		}
	}

	private static void handleAyncMessageBizException(AsyncRMBMessage<?> asyncMessage, BizErrors errors,
			ReloadableResourceBundleMessageSource bundleMessageSource, Locale locale) {

		StringBuilder sb = new StringBuilder();

		if (locale == null)
			locale = Locale.CHINESE;

		if (errors != null && errors.hasErrors()) {
			for (BizError error : errors.getAllErrors()) {
				sb.append("error code:").append(error.getCode()).append(",error msg:").append(bundleMessageSource
						.getMessage(error.getCode(), error.getArguments(), error.getDefaultMessage(), locale));
				LOG.error("publish aync message fail:" + sb.toString());
			}
		}

	}

}
